▶︎ 개요
도커 환경에서 Docke Compose를 사용하여 WAS를 scale out
하는 상황을 임의로 만들어보고,
nginx를 사용하여 스케일 아웃된 컨테이너들에게로 트래픽이 분산되도록 설정해보자.
▶︎ Application
테스트를 위해 시스템의 호스트명을 출력하는 간단한 애플리케이션 코드를 작성해보자.
해당 애플리케이션은 fastapi로 구동된다.
from fastapi import FastAPI
import socket
app : FastAPI = FastAPI()
@app.get('/hostname')
def get_hostname() -> dict[str,str]:
hostname = socket.gethostname()
return {'hostname': hostname}
/hostname
경로로 요청을 받는 app 코드를 작성하였다.
‣ Dockerfile 작성
위 애플리케이션을 도커라이징 하기 위해 도커 파일을 아래와 같이 작성하자. Dockerfile
FROM python:3.10-slim-buster
WORKDIR /usr/src/app
COPY ./requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY . .
CMD [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "4000"]
‣ Docker Compose 작성
아래와 같은 docker-compose.yaml
를 작성하자.
services:
app:
build: .
restart: always
ports:
- "4000:4000"
expose:
- "4000"
container_name: scale-app
volumes:
- type: bind
source: "./app"
target: "/usr/src/app"
모두 작성했다면 아래 명령어를 실행해 app 컨테이너를 실행시키자.
docker-compose up -d --build
▶︎ 애플리케이션 확장
서비스에 접근하는 유동 트래픽이 감당할 수 없을 만큼 늘어 성능을 확장해야 하는 상황이 왔다고 가정해보자.
이 때, 도커 컴포즈의 scale
옵션으로 간단하게 수평적 확장이 가능해진다.
docker-compose up --scale app=2
이 명령을 통해 scale out을 할 경우 자동으로 LB까지 구현 되는데 default 값은 round robin
방식이다.
또는 위 예시처럼 --scale
방법 말고 아래와 같이 docker-compose.yaml
파일에도 명시가 가능하다.
services:
app:
# ...
deploy:
replicas: 2
deploy.replicas
속성의 값을 2로 주었다. 이 내용은 docker-compose up --scale app=2
와 동일하다!
‣ 주의할 점
scale out
시에는 docker-compose.yaml
에 명시했던 외부 port를 모두 닫아야 한다.
하나의 외부 포트는 하나의 컨테이너만 사용할 수 있는데, 2개의 컨테이너가 같은 host port를 매핑하면 포트 충돌로 인해 정상적으로 실행되지 않는다.
$ docker-compose up -d
Error response from daemon: driver failed programming external connectivity on endpoint blue-green-app-2 (a3eafa456e190f9db22a91cb63e3186aa6afb1dececae16d8633bd4c567331c7): Bind for 0.0.0.0:4000 failed: port is already allocated
위처럼 두번째로 생성된 컨테이너에서 매핑된 호스트 port가 충돌되었다.
또한, container_name
속성을 줬다면 이 속성 또한 삭제해줘야 한다.
$ docker-compose up -d
WARNING: The "app" service is using the custom container name "scale-app". Docker requires each container to have a unique name. Remove the custom name to scale the service.
scale out시에는 container_name
지정이 불가능하다.
정상적으로 포트를 지우고 container_name
도 지우고 다시 실행한다면 아래와 같이 2대의 컨테이너가 생성된 것을 볼 수 있을 것이다.
▶︎ Nginx 설정
먼저 현재 경로에 nginx 폴더를 만들고 하위에 default.conf
파일을 아래와 같이 생성해주자.
# 폴더 생성
mkdir nginx
cd nginx
cat << EOF | tee default.conf
server {
listen 80;
server_name app;
location / {
proxy_pass http://app:4000;
}
}
EOF
nginx 설정의 경우 아주 간단하게 구성했다.
우리가 만든 app
을 등록하고, nginx 80번 포트로 요청이 들어오면 지정한 app으로 요청이 전달되도록 설정을 수정하였다.
‣ Docker Compose 작성
애플리케이션 Docker Compose 작성에서 작성했었던 코드에서 아래 내용을 추가해주자.
services:
app:
build: ./app
restart: always
expose:
- "4000"
volumes:
- type: bind
source: "./app"
target: "/usr/src/app"
deploy:
replicas: 2
nginx:
build: ./nginx
restart: always
ports:
- 80:80
volumes:
- type: bind
source: "nginx/default.conf"
target: "/etc/nginx/conf.d/default.conf"
container_name: nginx
다시 한번 docker-compose를 실행시켜보자.
docker-compose up -d --build
app 컨테이너가 2개, nginx 컨테이너가 1개 띄워진 것을 볼 수 있다.
nginx의 경우에는 scale out을 적용하지 않았다.
▶︎ 테스트
이제 띄워진 컨테이너에 요청을 보냈을 때 적절히 분산되어 들어가는지 확인해보자.
for i in $(seq 1 10); do curl -w "\n" localhost/hostname; sleep 1; done
같은 요청을 보냈지만 각기 다른 hostname이 출력되는 것이 보이는가?
서로 다른 컨테이너로 요청이 정상적으로 분산된 것이다.